package de.stefanocke.roo.kontaktliste.web.common;

import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.panel.FeedbackPanel;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;

import de.stefanocke.roo.wicket.RooStereotype;

@RooStereotype("wicket.Form")
public abstract class FormPanel<T> extends Panel {

	public class Form_ extends Form<T> {
		public Form_(String id) {
			super(id);
		}
	}

	private static final String FORM_ID = "form";

	private static final String FORMFIELDS_ID = "formfields";

	private static final String SAVE_ID = "save";

	private static final long serialVersionUID = 1L;

	protected FormPanel() {
		super("INVALID");
		throw new UnsupportedOperationException(
				"Do not use this constructor since it is only a workaround for ITD issues");
	}

	public FormPanel(String id, IModel<T> model, IEntityBindings<T, ?> serviceBindings) {
		super(id, model != null ? model : serviceBindings.createModel(null)); // TODO:
																				// Constructor without model -> form invisible?
		init(getModel(), serviceBindings);
	}

	private void init(IModel<T> model, IEntityBindings<T, ?> serviceBindings) {
		Form<T> form = createForm(serviceBindings.getSaveActions());
		form.add(createFeedbackPanel());
		FormFieldsPanel<T> formFields = createFormFields(model);
		form.add(formFields);

		add(form);
		// we set the model late so the children get informed...
		form.setModel(model);
		formFields.setModel(model);
		setOutputMarkupId(true);
	}

	@Override
	protected void onModelChanged() {
		// Propagate the model change to the form
		getForm().setModel(getModel());
		getFormFieldsPanel().setModel(getModel());
		super.onModelChanged();
	}

	protected FeedbackPanel createFeedbackPanel() {
		// TODO: As soon as WICKET-4799 is fixed, use
		// ContainerFeedbackMessageFilter.
		final FeedbackPanel feedbackPanel = new FeedbackPanel("feedback", null);
		feedbackPanel.setOutputMarkupId(true);
		feedbackPanel.add(new ConditionalCssAttributeBehavior() {

			@Override
			protected String getCssClass() {
				return "alert alert-error";
			}

			@Override
			protected boolean condition() {
				return feedbackPanel.anyMessage();
			}
		});
		return feedbackPanel;
	}

	protected abstract FormFieldsPanel<T> createFormFields(IModel<T> cmp);

	protected Form<T> createForm(List<IAction<IModel<T>, IModel<T>>> saveActions) {
		Form<T> form = new Form_(FORM_ID);

		// TODO: Allow multiple save / submit buttons?
		if (saveActions.size() != 1) {
			throw new IllegalArgumentException("Currently, only exactly one save action is supported");
		}
		final IAction<IModel<T>, IModel<T>> saveAction = saveActions.get(0);
		form.add(new ActionButton<IModel<T>, IModel<T>>(SAVE_ID, null) {

			@Override
			protected IModel<T> getParamForAction() {
				return FormPanel.this.getModel();
			}

			@Override
			protected IAction<IModel<T>, IModel<T>> getAction() {
				return saveAction;
			}

			@Override
			protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
				super.onSubmit(target, form);
				
				if (target != null) {
					// after successful save the form hides itself
					if(!form.hasFeedbackMessage()){
						setInnerVisible(false);
					}
					target.add(FormPanel.this);
				}
			}
			
			@Override
			protected void onActionException(RuntimeException e, Form<?> form) {
				// TODO Is this the right place here to convert JSR303 ConstraintViolations? Depending on the binding, other validation
				// exceptions might be used. Maybe JSR303 is the common denominator and we could map other validation exceptions to JSR 303.
				if(e instanceof ConstraintViolationException){
					ConstraintViolationException cve = (ConstraintViolationException) e;
					for (ConstraintViolation<?> cv : cve.getConstraintViolations()) {
						form.error(cv.getMessage());
					}
				
				} else {
					super.onActionException(e, form);
				}
			}

		});

		return form;
	}

	@Override
	public void onEvent(IEvent<?> event) {
		Object payload = event.getPayload();
		if (payload instanceof ActionEvent<?, ?>) {
			ActionEvent<?, ?> actionEvent = (ActionEvent<?, ?>) payload;
			String actionId = actionEvent.getAction().getActionId();
			if ((IEntityBindings.ACTION_ID_CREATE.equals(actionId) || IEntityBindings.ACTION_ID_UPDATE
					.equals(actionId)) && actionEvent.getResult() instanceof IModel<?>) {
				IModel<?> actionResult = (IModel<?>) actionEvent.getResult();
				if (actionResult.getObject().getClass() == getObjectClass()) { // AARGH!
					setDefaultModel(actionResult);
					setInnerVisible(true);
					if (actionEvent.getTarget() != null) {
						actionEvent.getTarget().add(FormPanel.this);
					}
				}
			} else {
				setInnerVisible(false);
				if (actionEvent.getTarget() != null) {
					actionEvent.getTarget().add(FormPanel.this);
				}

			}
			event.dontBroadcastDeeper();
		} else if(payload instanceof NavigationEvent<?>){
			NavigationEvent<?> ne = (NavigationEvent<?>) payload;
			setInnerVisible(false);
			if (ne.getTarget() != null) {
				ne.getTarget().add(FormPanel.this);
			}
		}
	}

	private Class<? extends Object> getObjectClass() {
		return getFormFieldsPanel().getObjectClass();
	}

	@SuppressWarnings("unchecked")
	public Form<T> getForm() {
		return (Form<T>) get(FORM_ID);
	}

	@SuppressWarnings("unchecked")
	public FormFieldsPanel<T> getFormFieldsPanel() {
		return (FormFieldsPanel<T>) getForm().get(FORMFIELDS_ID);
	}

	@SuppressWarnings("unchecked")
	public IModel<T> getModel() {
		return (IModel<T>) getDefaultModel();
	}

	/**
	 * Set the visibility of the content of the panel but let the panel itself
	 * visible. Useful for Ajax updates, since it allows to simply add the
	 * FormPanel to the AjaxRequestTarget instead of adding on more div around
	 * it.
	 * 
	 * @param visible
	 */
	public void setInnerVisible(boolean visible) {
		getForm().setVisible(visible);
	}

}
